/****************************************************************
 ** Product  :   HP Subscriber Network Application Policy
 ** Module   :   gr-manager
 ** Date: Apr 10, 2015               
 ** Author: Joey Yi
 ** (C) Copyright 2015, Hewlett-Packard Company, All Rights Reserved.
 ** This software is the proprietary information of HP, Inc.  
 ** Use is subject to license terms.
 *****************************************************************
 ** Description:   
 **   Prepare Groovy template and replace the template variable with context to generate a expression,
 **   The expression can be executed.
 **  
 ****************************************************************/
package com.hp.snap.gr.utils;

import com.hp.snap.gr.exception.ActionException;

import groovy.text.SimpleTemplateEngine;
import groovy.text.Template;

import org.apache.commons.lang.StringUtils;
import org.springframework.util.FileCopyUtils;


import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Prepare Groovy template and replace the template variable with context to generate a expression,
 * The expression can be executed.
 *
 * <P>
 * Version: 3.3 <br>
 * Author: Joey Yi
 * Date: Apr 10, 2015
 * 
 * </P>
 *
 **/
public class ExpressionHelper {
    private static final String TMP_VAR_PREFIX = "@@@@@";

    private static SimpleTemplateEngine engine = new SimpleTemplateEngine();
    private static ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("groovy");
    private static Map<String, Template> templateCacheMap = new ConcurrentHashMap<String, Template>();

    public static String executeTemplate(Map<String, Object> context, String expr) throws ActionException {
        if (StringUtils.isEmpty(expr)) {
            return expr;
        }

        try {
            Template template;
            String exprStr = preProcessTemplate(expr, context);
            if (templateCacheMap.containsKey(exprStr)) {
                template = templateCacheMap.get(exprStr);
            } else {
                template = engine.createTemplate(exprStr);
                templateCacheMap.put(exprStr, template);
            }

            String rs = template.make(context).toString();

            return postProcessTemplate(rs);

        } catch (Exception e) {
            throw new ActionException("Failed to execute template string: " + expr, e);
        }
    }

    private static final Pattern PRE_PROCESS_GROOVY_PATTERN = Pattern.compile("(\\$\\{\\s*([^\\$\\{]+)\\s*\\})");
    private static final Pattern PRE_PROCESS_SHELL_PATTERN = Pattern.compile("(\\$([^\\{\\s]+))");
    private static final Pattern PRE_PROCESS_TMP_PATTERN = Pattern.compile("(" + TMP_VAR_PREFIX + "([^\\s]+))");

    public static String preProcessTemplate(String templateString, Map<String, Object> context) {
        //find all groovy variables to check if contains in context
        StringBuffer sb = new StringBuffer();
        Matcher m = PRE_PROCESS_GROOVY_PATTERN.matcher(templateString);
        while (m.find()) {
            String key = m.group(2);
            key = key.indexOf(".") > 0 ? key.substring(0, key.indexOf(".")) : key;
            if (!context.containsKey(key)) {
                m.appendReplacement(sb, TMP_VAR_PREFIX + "{$2}");
            }
        }
        m.appendTail(sb);
        String str = sb.toString();
        sb = new StringBuffer();

        m = PRE_PROCESS_SHELL_PATTERN.matcher(str);
        while (m.find()) {
            m.appendReplacement(sb, TMP_VAR_PREFIX + "$2");
        }
        m.appendTail(sb);

        return sb.toString();
    }

    public static String postProcessTemplate(String templateString) {
        StringBuffer sb = new StringBuffer();
        Matcher m = PRE_PROCESS_TMP_PATTERN.matcher(templateString);
        while (m.find()) {
            m.appendReplacement(sb, "\\$$2");
        }
        m.appendTail(sb);

        return sb.toString();
    }

    public static String executeTemplate(Map<String, Object> context, File templateFile) throws ActionException {
        if (templateFile == null) {
            return null;
        }

        String fileName = templateFile.getPath();
        FileReader fdr = null;
        try {
            fdr = new FileReader(fileName);
            String expr = FileCopyUtils.copyToString(fdr);
            expr = preProcessTemplate(expr, context);
            Template template;
            if (templateCacheMap.containsKey(expr)) {
                template = templateCacheMap.get(expr);
            } else {
                template = engine.createTemplate(expr);
                templateCacheMap.put(expr, template);
            }

            String rs = template.make(context).toString();

            return postProcessTemplate(rs);

        } catch (Exception e) {
            throw new ActionException("Failed to execute template file: " + templateFile, e);
        }finally{
            try {
                if(fdr!=null)
                fdr.close();
            } catch (IOException e) {
            }
        }
    }

    public static Object executeExpression(Map<String, Object> context, String expression) throws ActionException {
        Object object;
        try {
            ScriptContext simpleScriptContext = new SimpleScriptContext();
            for (String key : context.keySet()) {
                simpleScriptContext.setAttribute(key, context.get(key), ScriptContext.ENGINE_SCOPE);
            }
            object = scriptEngine.eval(expression, simpleScriptContext);
        } catch (Exception e) {
            throw new ActionException("Failed to execute expression: " + expression, e);
        }
        return object;
    }
}
